﻿using System;
using System.Collections.Generic;
using System.Text;
using PW.Mini.SlepowronskiJ.Siec.Budowa;
using TerWoord.Diagnostics;

namespace PW.Mini.SlepowronskiJ.Siec.Algorytmy
{

    /// <summary>
    /// backpropagation - gradientowa metoda uczenia sieci neuronowej
    /// jest to przyklad rozbudowanej metody backpropagation, bo uwzgledniajacej
    /// moment, czyli poprzedni kierunek gradientu 
    /// w przypadku momentu =0 mamy podstawowa wersje backpropagation
    /// </summary>
    /// <remarks>
    /// <code>
    /// 
    ///                      propagacja sygnalu w sieci
    ///                    ------------------------->
    /// 
    ///        o ----- Sj = f(WSj) ----> o ----- Si = f(WSi) ----> o
    ///      Neuron j                Neuron i                   Neuron k
    ///    (warstwa L-1)           (warstwa L)               (warstwa L+1)
    /// 
    /// dla neuronu i :
    /// -------------------
    /// W[i,j](n+1) = W[i,j](n) + wspolczynnikUczenia * (sum w in wzorce(A(w)(i) * S(w)(j))) + 
    ///                 moment * ( W[i,j](n) - W[i,j](n-1) )
    /// 
    ///		objasnienia
    ///		Ai = f'(WSi) * (oczekiwane wyjscie - otrzymane wyjscie) dla warstwy ostatniej
    ///		Ai = f'(WSi) * SUM( Ak * W[k,i] )  - dla pozostalych warstw
    /// 
    /// </code>
    /// </remarks>
    [Serializable]
    public class BackPropagationAlgorytmUczenia4 : AlgorytmUczenia
    {
        #region konstruktor

        /// <summary>
        /// tworzy nowy obiekt klasy algorytmu wstecznej propagacji bledu 
        /// z parametrami wspolczynnikUczenia i momentu pobieranymi z Monitora
        /// </summary>
        public BackPropagationAlgorytmUczenia4()
            : base()
        {
            this.nazwa = "wsteczna propagacja błędu";
        }

        /// <summary>
        /// tworzy nowy obiekt klasy algorytmu wstecznej propagacji bledu dla
        /// sieci neuronowej
        /// z parametrami wspolczynnikUczenia i momentu pobieranymi z Monitora
        /// </summary>
        /// <param name="siecNeuronowa">siec neuronowa do uczenia</param>
        public BackPropagationAlgorytmUczenia4(SiecNeuronowa siecNeuronowa)
            : base(siecNeuronowa)
        {
            this.nazwa = "wsteczna propagacja błędu";
        }

        public override AlgorytmUczenia UtworzNowyAlgorytm()
        {
            return new BackPropagationAlgorytmUczenia4();
        }

        #endregion

        /// <summary>
        /// Metoda uczenia siec neuronowej z Walidacją na danych wejsciowych
        /// reprezentowanych przez macierz wartosci wraz z 
        /// oczekiwanymi wyjsciami dla tych danych 
        /// jesli wejscia lub wyjscia walidacyjne = null to bez walidacji
        /// </summary>
        /// <param name="wejscia">macierz wejsc testowych</param>
        /// <param name="wyjscia">macierz wyjsc oczekiwanych</param>
        /// <param name="wejsciaWalidacyjne">macierz wejsc walidacyjnych</param>
        /// <param name="wyjsciaWalidacyjne">macierz wyjsc walidacyjnych</param>
        public override void Uczenie(double[][] wejscia, double[][] wyjscia,
            double[][] wejsciaWalidacyjne, double[][] wyjsciaWalidacyjne)
        {
            base.Uczenie(wejscia, wyjscia, wejsciaWalidacyjne, wyjsciaWalidacyjne);
            double[] otrzymaneWyjscia;
            double bladTmp;
            double[][][] delta = new double[wejscia.Length][][];
            double[][][] ostatnieWejsciaNeuronow = new double[wejscia.Length][][];
            for (int i = 0; i < wejscia.Length; ++i)
            {
                delta[i] = new double[siecNeuronowa.RozmiarSieci][];
                ostatnieWejsciaNeuronow[i] = new double[siecNeuronowa.RozmiarSieci][];
                for (int j = 0; j < siecNeuronowa.RozmiarSieci; ++j)
                {
                    delta[i][j] = new double[siecNeuronowa[j].RozmiarWarstwy];
                    if (j == 0)
                        ostatnieWejsciaNeuronow[i][j] = wejscia[i];
                    else
                        ostatnieWejsciaNeuronow[i][j] = new double[siecNeuronowa[j].LiczbaWejsc];
                }
            }
            this.strategie.Clear();
            //this.strategie.Add(new WzrostBleduWalidacyjnegoStrategiaSN());
            this.strategie.Add(new AdaptacyjnyWspolczynnikUczeniaStrategiaSN());
            this.Init();
            do
            {
                this.PreIteration();
                bladTmp = 0.0;
                //todo losowanie kolejnosci wzorcow do poprawek
                //this.monitor.LosujKolejnosc(ref kolejnoscWzorcow);
                for (int i = 0; i < wejscia.Length; ++i)
                {
                    otrzymaneWyjscia = siecNeuronowa.Uruchom(wejscia[kolejnoscWzorcow[i]]);
                    bladTmp += Monitor.MiaryBledow.SrednioKwadratowySkroconyRefWB(
                        ref wektorBledow, wyjscia[kolejnoscWzorcow[i]], otrzymaneWyjscia);
                    WyliczA(kolejnoscWzorcow[i], delta, ostatnieWejsciaNeuronow);
                }
                UstawWagi(delta, ostatnieWejsciaNeuronow);
                this.monitor.BladSieci = bladTmp / wejscia.Length;
                this.PostIteration();
            }
            while (this.monitor.NumerIteracji < this.monitor.MaxLiczbaIteracji &&
                this.monitor.BladSieci > this.monitor.WarunekStopBleduSieci &&
                this.monitor.UczenieSieci);
            StringBuilder strBld = PobierzKomunikat();
            this.komunikat = strBld.ToString();
            this.monitor.Logger.SendString(komunikat);
        }

        /// <summary>
        /// wylicza wartosc A dla kazdego z neuronow
        /// </summary>
        /// <param name="i">indeks aktualnie uczonych danych</param>
        /// <param name="delta">wyliczane wspolczynniki delta do poprawy</param>
        /// <param name="ostatnieWejsciaNeuronow">pobierane ostatnie wejsca do neuronow</param>
        protected void WyliczA(int i, double[][][] delta, double[][][] ostatnieWejsciaNeuronow)
        {
            double sk;
            int l = siecNeuronowa.RozmiarSieci - 1;
            // dla ostatniej warstwy
            for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                delta[i][l][j] = siecNeuronowa[l][j].PochodnaWyjscia * wektorBledow[j];
            // dla pozostalych warstw
            for (--l; l >= 0; --l)
            {
                //warstwa l-ta
                for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                {
                    //neuron j-ty
                    sk = 0.0;
                    for (int k = 0; k < siecNeuronowa[l + 1].RozmiarWarstwy; ++k)
                        sk += delta[i][l + 1][k] * siecNeuronowa[l + 1][k][j];
                    delta[i][l][j] = siecNeuronowa[l][j].PochodnaWyjscia * sk;
                    ostatnieWejsciaNeuronow[i][l+1][j] = siecNeuronowa[l][j].Wartosc;
                }
            }
        }

        /// <summary>
        /// ustawia poprawione wagi
        /// </summary>
        /// <param name="delta">wyliczone wspolczynniki delta do poprawy</param>
        /// <param name="ostatnieWejsciaNeuronow">pobierane ostatnie wejsca do neuronow</param>
        protected void UstawWagi(double[][][] delta, double[][][] ostatnieWejsciaNeuronow)
        {
            for (int j = 0; j < siecNeuronowa.RozmiarSieci; ++j)
            {
                for (int n = 0; n < siecNeuronowa[j].RozmiarWarstwy; ++n)
                {
                    for (int k = 0; k < siecNeuronowa[j][n].LiczbaWejsc; ++k)
                    {
                        double sk = 0.0d;
                        for (int nr = 0; nr < delta.Length; ++nr)
                            sk += delta[nr][j][n] * ostatnieWejsciaNeuronow[nr][j][k];
                        siecNeuronowa[j][n][k] += (this.monitor.WspolczynnikUczenia * sk +
                            this.monitor.Moment * (siecNeuronowa[j][n][k] - siecNeuronowa[j][n].PoprzednieWagi[k]));
                    }
                }
            }
        }

    }

}
